package org.pf.constructor
{
	/**
	 * ...
	 * @author zeksa'
	 */
	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Collision.Shapes.b2PolygonShape;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2ContactListener;
	import Box2D.Dynamics.b2FixtureDef;
	import Box2D.Dynamics.b2World;
	import Box2D.Dynamics.Joints.b2DistanceJointDef;
	import Box2D.Dynamics.Joints.b2RevoluteJointDef;
	import org.farmcode.bezier.BezierPoint;
	import org.pf.base.pfBlocks.PFLevel;
	import org.pf.constructor.bodyDefs.B2DBodyDef;
	import org.pf.constructor.fixtureDefs.B2DFixtureDef;
	import org.pf.constructor.shapes.B2DCircleShape;
	import org.pf.constructor.shapes.B2DConvexShape;
	import org.pf.constructor.shapes.B2DPolygonShape;
	import org.pf.constructor.staff.B2DDebug;
	import org.pf.constructor.staff.PFBodyGeometry;
	import org.pf.constructor.staff.PFBodyUserData;
	import org.pf.interfaces.IPFConstructor;
	import org.pf.PFPrince;
	import org.pf.pfn_internal;
	
	public class PFConstructor implements IPFConstructor
	{
		private var _squareShape:b2PolygonShape;
		private var _circleShape:b2CircleShape;
		private var _convexShape:b2PolygonShape;
		
		private var _bodyDef:b2BodyDef;
		private var _fixtureDef:b2FixtureDef;
		
		private var _body:b2Body;
		private var _bodyGeometry:PFBodyGeometry;
		
		private var _level:PFLevel;
		
		public function PFConstructor()
		{
			PFPrince.getPrince().get_SystemCache().currentWorld = new b2World(new b2Vec2(0, 10), true);
			PFPrince.getPrince().get_SystemCache().currentWorld.SetDebugDraw(new B2DDebug(PFPrince.getPrince().get_SystemCache().layers.debugLayer));
		}
		
		public function setEnviroment(levelName:String):void
		{	
			PFPrince.getPrince().get_SystemCache().currentCache.setCurrentLevel(PFPrince.getPrince().get_SystemCache().currentCache.getLevel(levelName));
			_level = PFPrince.getPrince().get_SystemCache().currentCache.currentLevel;
		}
		
		public function addContactListener(customListener:b2ContactListener):void
		{
			PFPrince.getPrince().get_SystemCache().currentWorld.SetContactListener(customListener);
		}
		
		public function makeStageWalls(haveBackWalls:Array, haveBottomWalls:Array, backWallThickness:Number, bottomWallThickness:Number, gWidth:Number, gHeight:Number):void
		{
			for (var i:int = 0; i < 2; i++)
			{
				if (haveBackWalls[i] == true)
				{
					makePolygonal(backWallThickness, gHeight, i * (gWidth - backWallThickness) + backWallThickness / 2, gHeight / 2, 0, 0.5, 0.4, 0.5, false, false, "backWall" + i + 1, true, "backWall", "SQUARE");
				}
				if (haveBottomWalls[i] == true)
				{
					makePolygonal(gWidth, bottomWallThickness, gWidth / 2, i * (gHeight - bottomWallThickness) + bottomWallThickness / 2, 0, 0.5, 0.4, 0.5, false, false, "upperWall" + i + 1, true, "upperWall", "SQUARE");
				}
			}
		}
		
		public function startConstruction():void
		{
			var width:Number;
			var height:Number;
			var radius:Number;
			var vertices:Array;
			var posX:Number;
			var posY:Number;
			var rot:Number;
			var density:Number;
			var friction:Number;
			var restitution:Number;
			var isSensor:Boolean;
			var isDynamic:Boolean;
			var name:String;
			var hasImage:Boolean;
			var textureName:String;
			var type:String;
			
			var levelObjects:Array = _level.levelObjects;
			
			for (var i:int = 0; i < _level.levelObjects.length; i++)
			{
				//"------------------------------------" + i + "-------------------------------------------------");
				width = levelObjects[i][0];
				height = levelObjects[i][1];
				radius = levelObjects[i][2];
				vertices = levelObjects[i][3];
				posX = levelObjects[i][4];
				posY = levelObjects[i][5];
				rot = levelObjects[i][6];
				density = levelObjects[i][7];
				friction = levelObjects[i][8];
				restitution = levelObjects[i][9];
				isSensor = levelObjects[i][10];
				isDynamic = levelObjects[i][11];
				name = levelObjects[i][12];
				hasImage = levelObjects[i][13];
				textureName = levelObjects[i][14];
				type = levelObjects[i][15];
				if (type == "SQUARE")
				{
					_body=makePolygonal(width, height, posX, posY, rot, density, friction, restitution, isSensor, isDynamic, name, hasImage, textureName, type);
				}
				if (type == "CIRCLE")
				{
					_body=makeCircle(radius, posX, posY, rot, density, friction, restitution, isSensor, isDynamic, name, hasImage, textureName, type);
				}
				if (type == "CONVEX")
				{
					makeConvex(vertices, posX, posY, rot, density, friction, restitution, isSensor, isDynamic, name, hasImage, textureName, type);
				}
				if (type == "CONCAVE")
				{
					
				}
			}
			endConstruction();
		}
		
		private function endConstruction():void
		{
			_body = null;
			_bodyDef = null;
			_bodyGeometry = null;
		}
		
		private function b2dLinker(name:String, width:Number, height:Number, radius:Number, vertices:Array, posX:Number, posY:Number, rot:Number, mass:Number, fric:Number, restit:Number, sensor:Boolean = false, isDynamic:Boolean = false, type:String = null):b2Body
		{
			var bb:b2Body;
			_bodyDef = new B2DBodyDef(posX, posY, rot, isDynamic);
			_fixtureDef = new B2DFixtureDef(mass, fric, restit, sensor);
			if (type == "SQUARE")
			{
				_squareShape = new B2DPolygonShape(width, height);
				_fixtureDef.shape = _squareShape;
			}
			if (type == "CIRCLE")
			{
				_circleShape = new B2DCircleShape(radius);
				_fixtureDef.shape = _circleShape;
			}
			if (type == "CONVEX")
			{
				_convexShape = new B2DConvexShape(vertices);
				_fixtureDef.shape = _convexShape;
			}
			bb = PFPrince.getPrince().get_SystemCache().currentWorld.CreateBody(_bodyDef,name);
			bb.CreateFixture(_fixtureDef);
			return bb;
		}
		
		public function makePolygonal(width:Number, height:Number, posX:Number, posY:Number, rot:Number, mass:Number, fric:Number, restit:Number, sensor:Boolean = false, isDynamic:Boolean = false, name:String = null, hasImage:Boolean = false, textureName:String = null, type:String = null, applyImpulse:Boolean = false, impulse:b2Vec2 = null):b2Body
		{
			_body = b2dLinker(name,width, height, 0, [], posX, posY, rot, mass, fric, restit, sensor, isDynamic, type);
			if (applyImpulse)
			{
				_body.ApplyImpulse(impulse, _body.GetWorldCenter());
			}
			
			_bodyGeometry = new PFBodyGeometry(posX, posY, width, height, [[-width / 2, -height / 2], [width / 2, -height / 2], [width / 2, height / 2], [-width / 2, height / 2]], type, name, hasImage, textureName);
			_body.SetUserData(new PFBodyUserData(_bodyGeometry, PFPrince.getPrince().get_SystemCache().currentCache.sprites));
			if (hasImage)
			{
				PFPrince.getPrince().get_SystemCache().layers.viewLayer.addChild(_body.GetUserData().bodyGraphics.sprite);
			}
			_squareShape = null;
			_bodyDef = null;
			_fixtureDef = null;
			_bodyGeometry = null;
			return _body;
		}
		
		public function makeCircle(radius:Number, posX:Number, posY:Number, rot:Number, mass:Number, fric:Number, restit:Number, sensor:Boolean = false, isDynamic:Boolean = false, name:String = null, hasImage:Boolean = false, textureName:String = null, type:String = null, applyImpulse:Boolean = false, impulse:b2Vec2 = null):b2Body
		{
			_body = b2dLinker(name,0, 0, radius, [], posX, posY, rot, mass, fric, restit, sensor, isDynamic, type);
			if (applyImpulse)
			{
				_body.ApplyImpulse(impulse, _body.GetWorldCenter());
			}
			
			_bodyGeometry = new PFBodyGeometry(posX, posY, radius * 2, radius * 2, [[-radius, -radius], [radius, -radius], [radius, radius], [-radius, radius]], type, name, hasImage, textureName);
			_body.SetUserData(new PFBodyUserData(_bodyGeometry, PFPrince.getPrince().get_SystemCache().currentCache.sprites));
			if (hasImage)
			{
				PFPrince.getPrince().get_SystemCache().layers.viewLayer.addChild(_body.GetUserData().bodyGraphics.sprite);
			}
			_circleShape = null;
			_bodyDef = null;
			_fixtureDef = null;
			_bodyGeometry = null;
			return _body;
		}
		
		public function makeConvex(vertices:Array, posX:Number, posY:Number, rot:Number, mass:Number, fric:Number, restit:Number, sensor:Boolean = false, isDynamic:Boolean = false, name:String = null, hasImage:Boolean = false, textureName:String = null, type:String = null):void
		{
			_body = b2dLinker(name, 0, 0, 0, vertices, posX, posY, rot, mass, fric, restit, sensor, isDynamic, type);
			
			_bodyGeometry = new PFBodyGeometry(posX, posY, count_sizes(vertices)[0], count_sizes(vertices)[1], vertices, type, name, hasImage, textureName);
			_body.SetUserData(new PFBodyUserData(_bodyGeometry, PFPrince.getPrince().get_SystemCache().currentCache.sprites));
			if (hasImage)
			{
				PFPrince.getPrince().get_SystemCache().layers.viewLayer.addChild(_body.GetUserData().bodyGraphics.sprite);
			}
		}
		
		public function makeRevoluteJoint(bodyA:b2Body, bodyB:b2Body, anchorA:b2Vec2, anchorB:b2Vec2, isRope:Boolean, ropeName:String, isFirstLink:Boolean, isLastLink:Boolean):void
		{
			if (isRope)
			{
				var flag:Boolean = false;
				for (var i:int = 0; i < PFPrince.getPrince().get_SystemCache().currentCache.ropesNames.length; i++)
				{
					if (PFPrince.getPrince().get_SystemCache().currentCache.ropesNames[i] == ropeName)
					{
						flag = true;
					}
				}
				if (flag == false)
				{
					PFPrince.getPrince().get_SystemCache().currentCache.ropesNames.push(ropeName);
					PFPrince.getPrince().get_SystemCache().currentCache.ropes.push([[ropeName], []]);
					PFPrince.getPrince().get_SystemCache().currentCache.ropesBodies.push([]);
				}
				for (var k:int = 0; k < PFPrince.getPrince().get_SystemCache().currentCache.ropesNames.length; k++)
				{
					if (PFPrince.getPrince().get_SystemCache().currentCache.ropesNames[k] == ropeName)
					{
						if (isFirstLink)
						{
							PFPrince.getPrince().get_SystemCache().currentCache.ropesBodies[k].push(bodyA);
							PFPrince.getPrince().get_SystemCache().currentCache.ropes[k][0].push(anchorA, anchorB);
							PFPrince.getPrince().get_SystemCache().currentCache.ropes[k][1].push(new BezierPoint(bodyA.GetPosition().x * 30 + anchorA.x * 30, bodyA.GetPosition().y * 30 + anchorA.y * 30));
						}
						else if (isLastLink)
						{
							PFPrince.getPrince().get_SystemCache().currentCache.ropes[k][0][2] = anchorB;
							PFPrince.getPrince().get_SystemCache().currentCache.ropesBodies[k].push(bodyB);
							PFPrince.getPrince().get_SystemCache().currentCache.ropes[k][1].push(new BezierPoint(bodyB.GetPosition().x * 30 + anchorB.x * 30, bodyB.GetPosition().y * 30 + anchorB.y * 30));
						}
						else
						{
							PFPrince.getPrince().get_SystemCache().currentCache.ropes[k][1].push(new BezierPoint(bodyB.GetPosition().x * 30, bodyB.GetPosition().y * 30));
							PFPrince.getPrince().get_SystemCache().currentCache.ropesBodies[k].push(bodyB);
						}
					}
				}
			}
			var revoluteJointDef:b2RevoluteJointDef = new b2RevoluteJointDef();
			revoluteJointDef.localAnchorA.Set(anchorA.x, anchorA.y);
			revoluteJointDef.localAnchorB.Set(anchorB.x, anchorB.y);
			revoluteJointDef.bodyA = bodyA;
			revoluteJointDef.bodyB = bodyB;
			PFPrince.getPrince().get_SystemCache().currentWorld.CreateJoint(revoluteJointDef);
			revoluteJointDef = null;
		}
		
		public function makeDistanceJoint(bodyA:b2Body, bodyB:b2Body, anchorA:b2Vec2, anchorB:b2Vec2):void
		{
			var distanceJointDef:b2DistanceJointDef = new b2DistanceJointDef();
			distanceJointDef.length = 2;
			distanceJointDef.localAnchorA.Set(anchorA.x, anchorA.y);
			distanceJointDef.localAnchorB.Set(anchorB.x, anchorB.y);
			distanceJointDef.bodyA = bodyA;
			distanceJointDef.bodyB = bodyB;
			PFPrince.getPrince().get_SystemCache().currentWorld.CreateJoint(distanceJointDef);
			distanceJointDef = null;
		}
		
		public function makeRope(ropeLength:Number, numberOfLinks:Number, bodyA:b2Body, bodyB:b2Body, anchorA:b2Vec2, anchorB:b2Vec2, ropeName:String):void
		{
			var _segmLength:Number = ropeLength / 30 / numberOfLinks;
			var _ropeLength:Number = ropeLength / 30;
			var _segmNum:Number = numberOfLinks;
			anchorA.x = anchorA.x / 30;
			anchorA.y = anchorA.y / 30;
			anchorB.x = anchorB.x / 30;
			anchorB.y = anchorB.y / 30;
			(_segmNum);
			
			for (var i:Number = 0; i <= _segmNum; i++)
			{
				if (i == 0)
				{
					var link:b2Body = makePolygonal(10, 30 * _segmLength, bodyA.GetPosition().x * 30, (_segmLength + 2 * _segmLength * i) * 30, 0, 0.5, 0.5, 0.5, false, true, null, false, null, "SQUARE", false, null);
					makeRevoluteJoint(bodyA, link, anchorA, new b2Vec2(0, -_segmLength / 2), true, ropeName, true, false);
				}
				else if (i < _segmNum)
				{
					var newLink:b2Body = makePolygonal(10, 30 * _segmLength, bodyA.GetPosition().x * 30, (_segmLength + 2 * _segmLength * i) * 30, 0, 0.5, 0.5, 0.5, false, true, null, false, null, "CIRCLE", false, null);
					makeRevoluteJoint(link, newLink, new b2Vec2(0, _segmLength / 2), new b2Vec2(0, -_segmLength / 2), true, ropeName, false, false);
					link = newLink;
				}
				else
				{
					makeRevoluteJoint(link, bodyB, new b2Vec2(0, _segmLength / 2), anchorB, true, ropeName, false, true);
					link = newLink;
				}
			}
			link = null;
			newLink = null;
		}
		
		private function count_sizes(vertices:Array):Array
		{
			var maxX:Number = 0;
			var maxY:Number = 0;
			var sizes:Array;
			for (var i:int = 0; i < vertices.length; i++)
			{
				if ((i + 1) < vertices.length - 1)
				{
					if (vertices[i + 1][0] > vertices[i][0])
					{
						maxX = vertices[i + 1][0];
					}
					if (vertices[i + 1][1] > vertices[i][1])
					{
						maxY = vertices[i + 1][1];
					}
				}
			}
			sizes = [maxX, maxY];
			return sizes as Array;
		}
	}

}